package org.harryrose.resultkit;

import java.io.File;
import java.io.IOException;
import java.lang.reflect.Array;
import java.lang.reflect.Field;
import java.text.NumberFormat;
import java.util.Arrays;
import java.util.Formatter;
import java.util.HashSet;
import java.util.Locale;

import org.apache.commons.math.stat.descriptive.DescriptiveStatistics;
import org.harryrose.resultkit.annotations.*;
import org.harryrose.resultkit.converters.DataConverter;
import org.harryrose.resultkit.converters.NumericDataConverter;
import org.harryrose.resultkit.exceptions.CannotWriteException;
import org.harryrose.resultkit.exceptions.DimensionalityException;
import org.harryrose.resultkit.exceptions.TypeException;
import org.harryrose.resultkit.writers.CSVWriter;
import org.harryrose.resultkit.writers.ResultWriter;


public class ResultKit {
	
	private File baseDirectory;
	private File resultsDirectory;
	private File stateDirectory;
	private File parameterDirectory;
	private ResultWriter resultWriter;
	
	private boolean suppressWarnings = true;
	
	/**
	 * Enables or disables warnings when files cannot be written, or when exceptions occur while reading variables.
	 * 
	 * Exceptions include NullPointerExceptions, which will occur when trying to save a variable that has not 
	 * yet been assigned.  The default behaviour ResultKit is to catch all exceptions and (if this is enabled)
	 * output a warning on stderr.  This is to stop exceptions that occur while writing results from killing the
	 * JVM and consequently losing the simulation results.
	 * @param onoff True -- Warnings are Suppressed, False -- Warnings are sent to stderr
	 */
	public void setWarningSuppression(boolean onoff)
	{
		suppressWarnings = onoff;
	}
	
	private boolean suppressExceptions = true;
	
	/**
	 * Enables or disables suppression of exceptions.  If set to true, all exceptions that occur while writing data are caught and 
	 * suppressed by ResultKit.  If set to false, any exception is wrapped in a CannotWriteException and thrown.
	 * @param onoff
	 */
	public void setExceptionSuppression(boolean onoff)
	{
		suppressExceptions = onoff;
	}
	
	/**
	 * Creates a new ResultKit instance which will store any data in baseDirectoryName
	 * @param baseDirectoryName The directory in which to store results/states etc.
	 */
	public ResultKit(String baseDirectoryName)
	{
		this(baseDirectoryName, new CSVWriter());
	}
	
	/**
	 * Creates a new ResultKit instance which will store any data in baseDirectoryName, and writes the
	 * data files using the resultWriter instance.
	 * @param baseDirectoryName Directory in which to store results/states etc.
	 * @param resultWriter ResultWriter object that will write the files to disk.
	 */
	public ResultKit(String baseDirectoryName, ResultWriter resultWriter)
	{
		this(new File(baseDirectoryName), resultWriter);
	}
	
	/**
	 * Creates a new ResultKit instance which will store any data in resultDirectory
	 * @param resultDirectory Directory in which to store results and states etc.
	 */
	public ResultKit(File resultDirectory)
	{
		this(resultDirectory, new CSVWriter());
	}
	
	/**
	 * Creates a new ResultKit instance which will store any data in resultDirectory and writes the data
	 * files using the resultWriter instance.
	 * @param resultDirectory Directory in which to store results.
	 * @param resultWriter ResultWriter that will be used to write the files to the disk.
	 */
	public ResultKit(File resultDirectory,ResultWriter resultWriter)
	{
		this.baseDirectory = resultDirectory;
		baseDirectory.mkdirs();
		this.resultWriter = resultWriter;
		setResultsDirectory(new File(baseDirectory,"results"));
		setStateDirectory(new File(baseDirectory,"state"));
		setParameterDirectory(new File(baseDirectory,"parameters"));
	}
	
	/**
	 * Gets a unique filename that starts with name in the directory specified.
	 * @param directory Directory in which to create a new file.
	 * @param name The start of the name of the file
	 * @return
	 */
	public static File getUniqueFile(String directory,String name)
	{
		return getUniqueFile(new File(directory), name,3);
	}
	
	/**
	 * Gets a unique filename that starts with name in the directory specified.
	 * @param directory Directory in which to create a new file.
	 * @param name The start of the name of the file
	 * @param noDigits Number of digits (for zero padding)
	 * @return
	 */
	public static File getUniqueFile(File directory, String name, int noDigits)
	{
		return getUniqueFile(directory, name, noDigits, "-");
	}
	
	
	/**
	 * Gets a unique filename that starts with name in the directory specified.
	 * @param directory Directory in which to create a new file.
	 * @param name The start of the name of the file
	 * @param noDigits Number of digits (for zero padding)
	 * @param separator String to separate the base filename from the appended numbers
	 * @return
	 */
	public static  File getUniqueFile(File directory, String name, int noDigits,String separator)
	{
		return getUniqueFile(directory, name, noDigits, separator,true);
	}
	
	
	/**
	 * Gets a unique filename that starts with name in the directory specified.
	 * @param directory Directory in which to create a new file.
	 * @param name The start of the name of the file
	 * @param noDigits Number of digits (for zero padding)
	 * @param separator String to separate the base filename from the appended numbers
	 * @param zeroFill Whether or not to zero fill numbers
	 * @return
	 */
	public static File getUniqueFile(File directory, String name, int noDigits,String separator, boolean zeroFill)
	{
		int i = 0;
		File proposedFile;
		
		String formatString = "%s%s%"+(zeroFill? "0":"")+noDigits+"d";
		do
		{
			i++;
			StringBuilder sb = new StringBuilder();
			Formatter formatter = new Formatter(sb, Locale.UK);
			formatter.format(formatString, name,separator,i);
			proposedFile = new File(directory,sb.toString()); 
		}
		while(proposedFile.exists());
		
		return proposedFile;
	}
	
	private void setResultsDirectory(File dir)
	{
		this.resultsDirectory = dir;
		dir.mkdirs();
	}
	
	private void setParameterDirectory(File dir)
	{
		this.parameterDirectory = dir;
		dir.mkdirs();
	}
	
	private void setStateDirectory(File dir)
	{
		this.stateDirectory = dir;
		dir.mkdirs();
	}
	
	/**
	 * Returns the directory in which variables annotated with Result are stored
	 * @return
	 */
	public File getResultsDirectory()
	{
		return resultsDirectory;
	}
	
	/**
	 * Returns the directory in which variables *not* annotated with ExcludeFromStateSave are stored
	 * @return
	 */
	public File getStateDirectory()
	{
		return stateDirectory;
	}
	
	/**
	 * Returns the directory in which variables annotated with Parameter are stored  
	 * @return
	 */
	public File getParameterDirectory()
	{
		return parameterDirectory;
	}
	
	
	
	/**
	 * Saves all fields in an object annotated with Result, SaveMean or SaveVariance.
	 * @param object Object from which to store results
	 * @throws CannotWriteException Thrown if throwing exceptions is enabled and some exception occurs while writing the data.
	 */
	public void SaveResults(Object object) throws CannotWriteException
	{
		Class<?> obj = object.getClass();
		while(!obj.equals(Object.class))
		{
			for(Field f : obj.getDeclaredFields())
			{
				try
				{
					//If we should save the result
					f.setAccessible(true);
					if(f.isAnnotationPresent(Result.class))
					{
						DispatchSave(getResultsDirectory(), f, object, f.getAnnotation(Result.class).headings());
					}
					
					
					//If we should save statistics about the results
					
					if(f.isAnnotationPresent(SaveMean.class))
					{
						SaveMean(getResultsDirectory(), f, object, (f.getAnnotation(SaveMean.class).name().length() == 0 )? f.getName()+"_mean_"+f.getAnnotation(SaveMean.class).dimension() : f.getName());
					}
					
					if(f.isAnnotationPresent(SaveVariance.class))
					{
						SaveVariance(getResultsDirectory(), f, object, (f.getAnnotation(SaveVariance.class).name().length() == 0 )? f.getName()+"_variance_"+f.getAnnotation(SaveMean.class).dimension() : f.getName());
					}
					
					
					f.setAccessible(false);
				}
				catch(NullPointerException e) {
					if( !suppressWarnings ) System.err.println("ResultKit Warning: Caught a NullPointerException while writing result "+f.getName()+" of object "+obj.getClass().getCanonicalName() + "\n\t"+e.getMessage());
					if( !suppressExceptions ) throw new CannotWriteException(e);
				} catch(TypeException e) {
					if( !suppressWarnings ) System.err.println("ResultKit Warning: Caught a TypeException while writing statistics for "+f.getName()+" of object "+obj.getClass().getCanonicalName() + "\n\t"+e.getMessage() + "\n\t"+e.getMessage());
					if( !suppressExceptions ) throw new CannotWriteException(e);
				} catch (IllegalArgumentException e) {
					if( !suppressWarnings ) System.err.println("ResultKit Warning: Caught a IllegalArgumentException while writing statistics for "+f.getName()+" of object "+obj.getClass().getCanonicalName() + "\n\t"+e.getMessage());
					if( !suppressExceptions ) throw new CannotWriteException(e);
				} catch (IllegalAccessException e) {
					if( !suppressWarnings ) System.err.println("ResultKit Warning: Caught a IllegalAccessException while writing statistics for "+f.getName()+" of object "+obj.getClass().getCanonicalName() + "\n\t"+e.getMessage());
					if( !suppressExceptions ) throw new CannotWriteException(e);
				} catch (DimensionalityException e) {
					if( !suppressWarnings ) System.err.println("ResultKit Warning: Caught a DimensionalityException while writing statistics for "+f.getName()+" of object "+obj.getClass().getCanonicalName() + "\n\t"+e.getMessage());
					if( !suppressExceptions ) throw new CannotWriteException(e);
				} catch (CannotWriteException e) {
					if( !suppressWarnings ) System.err.println("ResultKit Warning: Caught a CannotWriteException while writing statistics for "+f.getName()+" of object "+obj.getClass().getCanonicalName() + "\n\t"+e.getMessage());
					if( !suppressExceptions ) throw new CannotWriteException(e);
				} catch (InstantiationException e) {
					if( !suppressWarnings ) System.err.println("ResultKit Warning: Caught a InstantiationException while writing statistics for "+f.getName()+" of object "+obj.getClass().getCanonicalName() + "\n\t"+e.getMessage());
					if( !suppressExceptions ) throw new CannotWriteException(e);
				}
			}
			obj = obj.getSuperclass();
		}
	}

	
	
	
	private void SaveMean(File directory, Field f, Object obj, String name) throws TypeException, CannotWriteException, IllegalArgumentException, IllegalAccessException, DimensionalityException, InstantiationException
	{
		int dimensionality = GetDimensionality(f, obj);
		int targetDimension = f.getAnnotation(SaveMean.class).dimension();
		NumericDataConverter dataConverter = f.getAnnotation(SaveMean.class).dataConverter().newInstance();
		f.setAccessible(true);
		Object target = f.get(obj);
		f.setAccessible(false);
		
		
		
		switch(dimensionality)
		{
			case 0:
				// Scalar
				resultWriter.write(directory, name, target);
				break;
			case 1:
			{
				DescriptiveStatistics stats = new DescriptiveStatistics();
				for(int i = 0; i < Array.getLength(target); i++)
				{
					stats.addValue(dataConverter.convert(Array.get(target,i)).doubleValue());
				}
				
				resultWriter.write(directory, name, stats.getMean());
			}
			break;
			
			case 2:
			{
				DescriptiveStatistics[] stats = getStatistics2D(target, targetDimension, dataConverter);
				Double[] out = new Double[stats.length];
				for(int i = 0; i < stats.length; i++)
				{
					out[i] = stats[i].getMean();
				}
				resultWriter.write(directory,name,out);			
	
			}
			break;
				
			case 3:
			{
				DescriptiveStatistics[][] stats = getStatistics3D(target, targetDimension, dataConverter);
				Double[][] out = new Double[stats.length][stats.length > 0? stats[0].length : 0];
				for(int x = 0; x < stats.length; x++)
				{
					for(int y = 0; y < stats[x].length; y++)
						out[x][y] = stats[x][y].getMean();
				}
				resultWriter.write(directory,name,out);
			}
			break;
			default:
				throw new DimensionalityException("Unsupported number of dimensions ("+dimensionality+")!");
		}
	}
	
	private  void SaveVariance(File directory, Field f, Object obj, String name) throws TypeException, CannotWriteException, IllegalArgumentException, IllegalAccessException, DimensionalityException, InstantiationException
	{
		int dimensionality = GetDimensionality(f, obj);
		int targetDimension = f.getAnnotation(SaveVariance.class).dimension();
		NumericDataConverter dataConverter = f.getAnnotation(SaveVariance.class).dataConverter().newInstance();
		
		f.setAccessible(true);
		Object target = f.get(obj);
		f.setAccessible(false);

		switch(dimensionality)
		{
			case 0:
				// Scalar
				resultWriter.write(directory, name, target);
				break;
			case 1:
			{
				DescriptiveStatistics stats = new DescriptiveStatistics();
				for(int i = 0; i < Array.getLength(target); i++)
				{
					stats.addValue(dataConverter.convert(Array.get(target,i)).doubleValue());
				}
				
				resultWriter.write(directory, name, stats.getVariance());
			}
			break;
			
			case 2:
			{
				DescriptiveStatistics[] stats = getStatistics2D(target, targetDimension,dataConverter);
				Double[] out = new Double[stats.length];
				for(int i = 0; i < stats.length; i++)
				{
					out[i] = stats[i].getVariance();
				}
				resultWriter.write(directory,name,out);
			}
			break;
				
			case 3:
			{
				DescriptiveStatistics[][] stats = getStatistics3D(target, targetDimension,dataConverter);
				Double[][] out = new Double[stats.length][stats.length > 0? stats[0].length : 0];
				for(int x = 0; x < stats.length; x++)
				{
					for(int y = 0; y < stats[x].length; y++)
						out[x][y] = stats[x][y].getVariance();
				}
				resultWriter.write(directory,name,out);
			}
			break;
			
			default:
				throw new DimensionalityException("Unsupported number of dimensions ("+dimensionality+")!");
		}
	}
	
	private DescriptiveStatistics[] getStatistics2D(Object target, int targetDimension,NumericDataConverter dataConverter) throws ArrayIndexOutOfBoundsException, IllegalArgumentException, TypeException
	{
		
			DescriptiveStatistics[] stats = null;
			switch(targetDimension)
			{
			case 0:
				// out[0] = (1/n) ( [0,0] + [1, 0] + [2, 0] ....
				// out[1] = (1/n) ( [0,1] + [1, 1] + [2, 1] ....
				// Length of output is equal to the length of the second dimension
				{
					
					int longest = 0;
					for(int i = 0; i < Array.getLength(target); i ++)
					{
						if(Array.getLength(Array.get(target, i)) > longest)
						{
							longest = Array.getLength(Array.get(target, i));
						}
					}
					
					 stats = new DescriptiveStatistics[longest];
					
					for(int x = 0; x < Array.getLength(target); x ++)
					{
						for(int y = 0; y < Array.getLength(Array.get(target,x)); y ++)
						{
							if(stats[y] == null) stats[y] = new DescriptiveStatistics();
							stats[y].addValue(dataConverter.convert(Array.get(Array.get(target, x),y)).doubleValue());
						}
					}
				}
				break;
			case 1:
				// out[0] = (1/n) ( [0,0] + [0,1] + [0,2] ....
				// out[1] = (1/n) ( [1,0] + [1,1] + [1,2] ....
				//Length of output is equal to the length of the first dimension
				{
					stats = new DescriptiveStatistics[Array.getLength(target)];
					
					for(int x = 0; x < Array.getLength(target); x ++)
					{
						for(int y = 0; y < Array.getLength(Array.get(target,x)); y ++)
						{
							if(stats[x] == null) stats[x] = new DescriptiveStatistics();
							stats[x].addValue(dataConverter.convert(Array.get(Array.get(target, x),y)).doubleValue());
						}
					}
				}
				break;
			}
			
			return stats;
	}
	
	
	private DescriptiveStatistics[][] getStatistics3D(Object target, int targetDimension,NumericDataConverter dataConverter) throws ArrayIndexOutOfBoundsException, IllegalArgumentException, TypeException
	{
	
		DescriptiveStatistics[][] stats = null;
		switch(targetDimension)
		{
		case 0:
			// out[0][0] = (1/n) ( [0,0,0] + [1, 0, 0] + [2, 0, 0] ....
			// out[1][0] = (1/n) ( [0,1,0] + [1, 1, 0] + [2, 1, 0] ....
			// out[1][1] = (1/n) ( [0,1,1] + [1, 1, 1] + [2, 1, 1] ....
			
			// Dimensions of output are
			//	1:  length of second dimension
			//  2:  length of third dimension
			
			{
				
				int longest1 = 0;
				int longest2 = 0;
				
				for(int i = 0; i < Array.getLength(target); i ++)
				{
					if(Array.getLength(Array.get(target, i)) > longest1)
					{
						longest1 = Array.getLength(Array.get(target, i));
					}
					
					for(int j = 0; j < Array.getLength(Array.get(target,i)); j++)
					{
						if(longest2 < Array.getLength(Array.get(Array.get(target,i),j)))
						{
							longest2 = Array.getLength(Array.get(Array.get(target,i),j));
						}
					}
				}
				
				
				stats = new DescriptiveStatistics[longest1][longest2];
				
				for(int x = 0; x < Array.getLength(target); x ++)
				{
					for(int y = 0; y < Array.getLength(Array.get(target,x)); y ++)
					{
						for(int z = 0; z < Array.getLength(Array.get(Array.get(target,x),y));  z ++)
						{
							if(stats[y][z] == null) stats[y][z] = new DescriptiveStatistics();
							
							stats[y][z].addValue(dataConverter.convert(Array.get(Array.get(Array.get(target, x),y),z)).doubleValue());
						}
					}
				}
			}
			break;
		case 1:
			// out[0][0] = (1/n) ( [0,0,0] + [0, 1, 0] + [0, 2, 0] ....
			// out[1][0] = (1/n) ( [1,0,0] + [1, 1, 0] + [1, 2, 0] ....
			// out[1][1] = (1/n) ( [1,0,1] + [1, 1, 1] + [1, 2, 1] ....
			
			// Dimensions of output are
			//	1:  length of first dimension
			//  2:  length of third dimension
			
			{
				
				int longest1 = Array.getLength(target);
				int longest2 = 0;
				
				for(int i = 0; i < Array.getLength(target); i ++)
				{
					for(int j = 0; j < Array.getLength(Array.get(target,i)); j++)
					{
						if(longest2 < Array.getLength(Array.get(Array.get(target,i),j)))
						{
							longest2 = Array.getLength(Array.get(Array.get(target,i),j));
						}
					}
				}
				
				
				stats = new DescriptiveStatistics[longest1][longest2];
				
				for(int x = 0; x < Array.getLength(target); x ++)
				{
					for(int y = 0; y < Array.getLength(Array.get(target,x)); y ++)
					{
						for(int z = 0; z < Array.getLength(Array.get(Array.get(target,x),y));  z ++)
						{
							if(stats[x][z] == null) stats[x][z] = new DescriptiveStatistics();
							stats[x][z].addValue(dataConverter.convert(Array.get(Array.get(Array.get(target, x),y),z)).doubleValue());
						}
					}
				}
			}
			
		case 2:
			// out[0][0] = (1/n) ( [0,0,0] + [0, 0, 1] + [0, 0, 2] ....
			// out[1][0] = (1/n) ( [1,0,0] + [1, 0, 1] + [1, 0, 2] ....
			// out[1][1] = (1/n) ( [1,1,0] + [1, 1, 1] + [1, 1, 2] ....
			
			// Dimensions of output are
			//	1:  length of first dimension
			//  2:  length of second dimension
			
			{
				
				int longest1 = Array.getLength(target);
				int longest2 = 0;
				
				for(int i = 0; i < Array.getLength(target); i ++)
				{
					for(int j = 0; j < Array.getLength(Array.get(target,i)); j++)
					{
						if(longest2 < Array.getLength(Array.get(target,i)))
						{
							longest2 = Array.getLength(Array.get(target,i));
						}
					}
				}
				
				
				stats = new DescriptiveStatistics[longest1][longest2];
				
				for(int x = 0; x < Array.getLength(target); x ++)
				{
					for(int y = 0; y < Array.getLength(Array.get(target,x)); y ++)
					{
						for(int z = 0; z < Array.getLength(Array.get(Array.get(target,x),y));  z ++)
						{
							if(stats[x][y] == null) stats[x][y] = new DescriptiveStatistics();
							stats[x][y].addValue(dataConverter.convert(Array.get(Array.get(Array.get(target, x),y),z)).doubleValue());
						}
					}
				}
			}
			break;
		}
		
		return stats;
	}

	
	/**
	 * Saves all variables in an object that are not annotated with ExcludeFromStateSave.
	 * This is particularly useful if you need to store intermediary values after each iteration in case of
	 * a program crash.  Potentially uses a lot of I/O!
	 * @param object Object from which to store variables
	 * @throws CannotWriteException Thrown if throwing exceptions is enabled and some exception occurs while writing the data.
	 * @throws TypeException 
	 */
	public void SaveState(Object object) throws CannotWriteException, TypeException
	{
		Class<?> obj = object.getClass();
		while(!obj.equals(Object.class))
		{
			for(Field f : obj.getDeclaredFields())
			{
				try
				{
					f.setAccessible(true);
					if(!f.isAnnotationPresent(ExcludeFromStateSave.class))
					{
						DispatchSave(getStateDirectory(), f, object, new String[0]);
					}
					f.setAccessible(false);
				} catch(NullPointerException e) {
					if( !suppressWarnings ) System.err.println("ResultKit Warning: Caught a NullPointerException while writing state variable "+f.getName()+" of object "+obj.getClass().getCanonicalName());
					if( !suppressExceptions ) throw new CannotWriteException(e);
				} catch(CannotWriteException e) {
					if( !suppressWarnings ) System.err.println("ResultKit Warning: Caught a CannotWriteException while writing state variable "+f.getName()+" of object "+obj.getClass().getCanonicalName());
					if( !suppressExceptions ) throw new CannotWriteException(e);
				} catch(DimensionalityException e){
					if( !suppressWarnings ) System.err.println("ResultKit Warning: Caught a DimensionalityException while writing state variable "+f.getName()+" of object "+obj.getClass().getCanonicalName());
					if( !suppressExceptions ) throw new CannotWriteException(e);
				}
				
				
			}
			obj = obj.getSuperclass();
		}
	}

	/**
	 * Saves all variables in an object annotated with Parameter 
	 * @param object Object from which to store variables
	 * @throws CannotWriteException Thrown if throwing exceptions is enabled and some exception occurs while writing the data.
	 * @throws TypeException 
	 */
	public void SaveParameters(Object object) throws CannotWriteException, TypeException
	{
		
		Class<?> obj = object.getClass();
		while(!obj.equals(Object.class))
		{
			for(Field f : obj.getDeclaredFields())
			{
				try
				{
					f.setAccessible(true);
					if(f.isAnnotationPresent(Parameter.class))
					{
						DispatchSave(getParameterDirectory(), f, object, f.getAnnotation(Parameter.class).headings());
					}
					f.setAccessible(false);
				} catch(NullPointerException e) {
					if( !suppressWarnings ) System.err.println("ResultKit Warning: Caught a NullPointerException while writing parameter "+f.getName()+" of object "+obj.getCanonicalName());
					if( !suppressExceptions ) throw new CannotWriteException(e);
				} catch(DimensionalityException e){
					if( !suppressWarnings ) System.err.println("ResultKit Warning: Caught a DimensionalityException while writing parameter "+f.getName()+" of object "+obj.getCanonicalName());
					if( !suppressExceptions ) throw new CannotWriteException(e);
				}
			}
			obj = obj.getSuperclass();
		}
	}
	
	private void DispatchSave(File directory, Field f, Object object, String[] headings) throws DimensionalityException, CannotWriteException, TypeException
	{
		if(!directory.exists())
		{
			directory.mkdirs();
		}
		
		if(headings != null && headings.length > 0)
		{
			resultWriter.write(directory, f.getName(), headings);
		}
		
		int dim = 0;
		f.setAccessible(true);
		try
		{
			switch(dim = GetDimensionality(f, object))
			{
			case 0: // Scalar
				
				resultWriter.write(directory, f.getName(), f.get(object));
				break;
				
			case 1: // 1D
			{
				Object[] obj;
				if(!isPrimitive(f.get(object).getClass()))
				{
					obj = ((new Object[0]).getClass().cast(f.get(object)));
				}
				else
				{
					obj = toObject1D(f.get(object));
				}
				
				resultWriter.write(directory,f.getName(),obj); 
			}
			break;
				
			case 2: // 2D
			{
				Object[][] obj;
				if(!isPrimitive(f.get(object).getClass()))
				{
					obj = (new Object[0][0]).getClass().cast(f.get(object));
				}
				else
				{
					obj = toObject2D(f.get(object));
				}
				resultWriter.write(directory,f.getName(), obj);
			}
			break;
				
			default:
				f.setAccessible(false);
				throw new DimensionalityException("Unsupported number of array dimensions: Field '"+f.getName()+"' of class '"+object.getClass().getCanonicalName()+"' has "+dim+" dimensions, and thus cannot be saved.");
			}
		}
		catch(IllegalAccessException e)
		{
			
		}
		
		f.setAccessible(false);
	}
	
	private int GetDimensionality(Field f, Object object)
	{
		int dim = 0;
		
		Class<?> type = f.getType();
		while(type.isArray())
		{
			dim ++;
			type = type.getComponentType();
		}
		
		return dim;
	}
	
	private boolean isPrimitive(Class<?> o)
	{
		if(o.isArray())
		{
			return isPrimitive(o.getComponentType());
		}
		else
		{
			return o.isPrimitive();
		}
	}
	
	private Object[] toObject1D(Object o)
	{
		if(o.getClass().isArray())
		{
			Object[] out = new Object[Array.getLength(o)];
			for(int i = 0; i < Array.getLength(o); i++)
				out[i] = Array.get(o, i);
			
			return out;
		}
		else
		{
			return new Object[]{o};
		}
	}
	
	private Object[][] toObject2D(Object o)
	{
		if(o.getClass().isArray())
		{
			int ilength = Array.getLength(o);
			int jlength;
			if(o.getClass().getComponentType().isArray())
			{
				jlength = Array.getLength(Array.get(o, 0));
			}
			else
			{
				jlength = 1;
			}
			
			Object[][] out = new Object[ilength][jlength];
			
			for(int i = 0; i < ilength; i++)
			{
				for(int j = 0; j < jlength; j++)
				{
					out[i][j] = Array.get(Array.get(o, i), j);
				}
			}
			return out;
		}
		return new Object[][]{{o}};
	}
}
